package com.jlf.core.engine.builder;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.Version;
import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
import com.baomidou.mybatisplus.core.conditions.interfaces.Compare;
import com.baomidou.mybatisplus.core.conditions.interfaces.Join;
import com.baomidou.mybatisplus.core.conditions.interfaces.Nested;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.jlf.core.annotation.Domain;
import com.jlf.core.annotation.restful.controller.RestfulController;
import com.jlf.core.annotation.restful.method.*;
import com.jlf.core.domain._SimpleDO;
import com.jlf.core.domain._VersionSimpleDO;
import com.jlf.core.engine.constant.SchemaConstant;
import com.jlf.core.engine.domain.SchemaCriterionDO;
import com.jlf.core.engine.domain.SchemaFieldDO;
import com.jlf.core.error.exception.NoObjectException;
import com.jlf.core.mp.mapper._SimpleDao;
import com.jlf.core.mp.persistence._SimplePersistence;
import com.jlf.core.mp.persistence.impl._SimplePersistenceImpl;
import com.jlf.core.server._SimpleServer;
import com.jlf.core.server.entity.*;
import com.jlf.core.server.impl._SimpleServerImpl;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.Annotation;
import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringUtils;
import org.objectweb.asm.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;

import java.io.Serializable;
import java.lang.invoke.LambdaMetafactory;
import java.lang.reflect.Field;
import java.util.function.Consumer;

/**
 * 自动构建的辅助工具
 *
 * @author wujr
 * 2023/7/9
 * @version 1.0
 * 【变更历史】
 * [wujr 2023/7/9 1.0 新增]
 */
public class AutoBuilderTool {
    public static String _asmSimpleDaoInternal = Type.getInternalName(_SimpleDao.class);
    public static String _asmSimpleInternal = Type.getInternalName(_SimpleDO.class);// RegExUtils.replaceAll(_SimpleDO.class.getName(), "\\.", "/");
    public static String _asmSimplePersistenceInternal = Type.getInternalName(_SimplePersistence.class);// RegExUtils.replaceAll(_SimplePersistence.class.getName(), "\\.", "/");
    public static String _asmSimplePersistenceImplInternal = Type.getInternalName(_SimplePersistenceImpl.class);// RegExUtils.replaceAll(_SimplePersistenceImpl.class.getName(), "\\.", "/");
    public static String _asmSimpleServerInternal = Type.getInternalName(_SimpleServer.class);// RegExUtils.replaceAll(_SimpleServer.class.getName(), "\\.", "/");
    public static String _asmSimpleServerImplInternal = Type.getInternalName(_SimpleServerImpl.class);
    public static String _asmExceptionInternal = Type.getInternalName(Exception.class);
    public static String _asmQInternal = Type.getInternalName(Q.class);
    public static String _asmQueryWrapperInternal = Type.getInternalName(QueryWrapper.class);
    public static String _asmNoObjectExceptionInternal = Type.getInternalName(NoObjectException.class);
    public static String _asmLambdaQueryWrapperInternal = Type.getInternalName(LambdaQueryWrapper.class);
    public static String _asmAbstractWrapperInternal = Type.getInternalName(AbstractWrapper.class);
    public static String _asmCompareInternal = Type.getInternalName(Compare.class);
    public static String _asmNestedInternal = Type.getInternalName(Nested.class);
    public static String _asmJoinInternal = Type.getInternalName(Join.class);
    public static String _asmStringUtilsInternal = Type.getInternalName(StringUtils.class);
    public static String _asmLambdaMetafactoryInternal = Type.getInternalName(LambdaMetafactory.class);
    public static String _asmObjectInternal = Type.getInternalName(Object.class);
    public static String _asmStringType = Type.getDescriptor(String.class);
    public static String _asmSimpleType = Type.getDescriptor(_SimpleDO.class);
    public static String _asmGetLongDesc = "()" + Type.getDescriptor(Long.class);
    public static String _asmSetLongDesc = "(" + Type.getDescriptor(Long.class) + ")V";
    public static String _asmGetIntegerDesc = "()" + Type.getDescriptor(Integer.class);
    public static String _asmSetIntegerDesc = "(" + Type.getDescriptor(Integer.class) + ")V";
    public static String _asmSerializableType = Type.getDescriptor(Serializable.class);
    public static String _asmVersionSimpleType = Type.getDescriptor(_VersionSimpleDO.class);
    public static String _asmLambdaMetafactoryType = Type.getDescriptor(LambdaMetafactory.class);
    public static String _asmLambdaMetafactoryStaticMethodDesc = "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;";
    public static String _asmStringUtilsType = Type.getDescriptor(StringUtils.class);
    public static String _asmQueryWrapperType = Type.getDescriptor(QueryWrapper.class);
    public static String _asmLambdaQueryWrapperType = Type.getDescriptor(LambdaQueryWrapper.class);
    public static String _asmSimpleDaoType = Type.getDescriptor(_SimpleDao.class);
    public static String _asmSimplePersistenceType = Type.getDescriptor(_SimplePersistence.class);
    public static String _asmSimplePersistenceImplType = Type.getDescriptor(_SimplePersistenceImpl.class);
    public static String _asmSimpleServerType = Type.getDescriptor(_SimpleServer.class);
    public static String _asmSimpleServerImplType = Type.getDescriptor(_SimpleServerImpl.class);
    public static String _asmDomainType = Type.getDescriptor(Domain.class);
    public static String _asmTableFieldType = Type.getDescriptor(TableField.class);
    public static String _asmVersionType = Type.getDescriptor(Version.class);
    public static String _asmAddRecordWrapperType = Type.getDescriptor(AddRecordWrapper.class);
    public static String _asmEditRecordWrapperType = Type.getDescriptor(EditRecordWrapper.class);
    public static String _asmDeleteRecordWrapperType = Type.getDescriptor(DeleteRecordWrapper.class);
    public static String _asmQType = Type.getDescriptor(Q.class);
    public static String _asmIntegerType = Type.getDescriptor(Integer.class);
    public static String _asmListWrapperType = Type.getDescriptor(ListWrapper.class);
    public static String _asmTagType = Type.getDescriptor(Tag.class);
    public static String _asmOverrideType = Type.getDescriptor(Override.class);
    public static String _asmTransactionType = Type.getDescriptor(Transactional.class);
    public static String _asmNotBlankType = Type.getDescriptor(NotBlank.class);
    public static String _asmNotNullType = Type.getDescriptor(NotNull.class);
    public static String _asmAutowiredType = Type.getDescriptor(Autowired.class);
    public static String _asmValueType = Type.getDescriptor(Value.class);
    public static String _asmSFunctionType = Type.getDescriptor(SFunction.class);
    public static String _asmConsumerType = Type.getDescriptor(Consumer.class);
    public static String _asmExceptionType = Type.getDescriptor(Exception.class);
    public static String _asmRuntimeExceptionType = Type.getDescriptor(RuntimeException.class);
    public static String _asmValidType = Type.getDescriptor(Valid.class);
    public static String _asmPathVariableType = Type.getDescriptor(PathVariable.class);
    public static String _asmRequestBodyType = Type.getDescriptor(RequestBody.class);
    public static String _asmRestfulControllerType = Type.getDescriptor(RestfulController.class);
    public static String _asmRequestParamType = Type.getDescriptor(RequestParam.class);
    public static String _asmRestfulPostType = Type.getDescriptor(RestfulPost.class);
    public static String _asmRestfulGetType = Type.getDescriptor(RestfulGet.class);
    public static String _asmRestfulPutType = Type.getDescriptor(RestfulPut.class);
    public static String _asmRestfulDeleteType = Type.getDescriptor(RestfulDelete.class);
    public static String _asmRestfulPatchType = Type.getDescriptor(RestfulPatch.class);
    public static String _asmNoObjectExceptionType = Type.getDescriptor(NoObjectException.class);
    public static String _asmQueryWrapperSignature = _asmQueryWrapperType.substring(0, _asmQueryWrapperType.length() - 1);
    public static String _asmLambdaQueryWrapperSignature = _asmLambdaQueryWrapperType.substring(0, _asmLambdaQueryWrapperType.length() - 1);
    public static String _asmSimpleSignature = _asmSimpleType.substring(0, _asmSimpleType.length() - 1);
    public static String _asmListWrapperSignature = _asmListWrapperType.substring(0, _asmListWrapperType.length() - 1);
    public static String _asmVersionSimpleSignature = _asmVersionSimpleType.substring(0, _asmVersionSimpleType.length() - 1);
    public static String _asmSimpleDaoSignature = _asmSimpleDaoType.substring(0, _asmSimpleDaoType.length() - 1);
    public static String _asmSimplePersistenceSignature = _asmSimplePersistenceType.substring(0, _asmSimplePersistenceType.length() - 1);
    public static String _asmSimplePersistenceImplSignature = _asmSimplePersistenceImplType.substring(0, _asmSimplePersistenceImplType.length() - 1);
    public static String _asmSimpleServerSignature = _asmSimpleServerType.substring(0, _asmSimpleServerType.length() - 1);
    public static String _asmSimpleServerImplSignature = _asmSimpleServerImplType.substring(0, _asmSimpleServerImplType.length() - 1);
    public static String _asmAddRecordWrapperSignature = _asmAddRecordWrapperType.substring(0, _asmAddRecordWrapperType.length() - 1);
    public static String _asmEditRecordWrapperSignature = _asmEditRecordWrapperType.substring(0, _asmEditRecordWrapperType.length() - 1);
    public static String _asmDeleteRecordWrapperSignature = _asmDeleteRecordWrapperType.substring(0, _asmDeleteRecordWrapperType.length() - 1);
    public static String _asmQSignature = _asmQType.substring(0, _asmQType.length() - 1);
    public static final String _asmInitName = "<init>";
    public static final String _asmInitDesc = "()V";
    public static final String _asmStringBooleanDesc = "(" + Type.getDescriptor(String.class) + ")" + Type.getDescriptor(boolean.class);
    public static final String _asmCharSequenceBooleanDesc = "(" + Type.getDescriptor(CharSequence.class) + ")Z";
    public static final String _asmDomainVersion = "_domainVersion";
    public static final String _asmCompareDesc = "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;";
    public static final String _asmCompareAndLambdaDesc = "(Ljava/util/function/Consumer;)Ljava/lang/Object;";
    public static final String _asmQueryWrapperAndLambdaDesc = "(Ljava/lang/Object;)" + Type.getDescriptor(QueryWrapper.class);
    public static final String _asmCompareOrDesc = "()Ljava/lang/Object;";
    public static final String _asmLambdaQueryWrapperCompareDesc = "(Ljava/lang/Object;Ljava/lang/Object;)" + Type.getDescriptor(LambdaQueryWrapper.class);
    public static final String _asmAbstractWrapperCompareDesc = "(Ljava/lang/Object;Ljava/lang/Object;)" + Type.getDescriptor(AbstractWrapper.class);
    public static final String _asmLambdaQueryWrapperAndDesc = "(Ljava/util/function/Consumer;)" + Type.getDescriptor(LambdaQueryWrapper.class);
    public static final String _asmCompareCompareDesc = "(Ljava/lang/Object;Ljava/lang/Object;)" + Type.getDescriptor(Compare.class);
    public static final String _asmLambdaQueryWrapperOrDesc = "()" + Type.getDescriptor(LambdaQueryWrapper.class);
    public static final SchemaFieldDO _optimisticVersionField = new SchemaFieldDO();

    static{
        _optimisticVersionField.setClassFieldCode("optimisticVersion");
        _optimisticVersionField.setJdbcFieldCode("optimistic_version");
        _optimisticVersionField.setClassDataType(SchemaConstant.Java.Type.INTEGER);
        _optimisticVersionField.setJdbcFieldType(SchemaConstant.Jdbc.Type.INTEGER);
        _optimisticVersionField.setDefaultValue("1");
    }

    public static String class2Asm(String className){
        return RegExUtils.replaceAll(className, "\\.", "/");
    }
    public static String class2Asm(Class<?> clz){
        return Type.getInternalName(clz);
    }
    public static void asmDefaultConstructor(ClassWriter cw, String asmClassName, boolean callSuper){
        //生成默认的构造方法
        //第一个参数是 ACC_PUBLIC ， 指定要生成的方法的访问标志。 这个参数对应method_info 中的access_flags 。
        //第二个参数是方法的方法名。 对于构造方法来说， 方法名为<init> 。 这个参数对应method_info 中的name_index ， name_index引用常量池中的方法名字符串。
        //第三个参数是方法描述符， 在这里要生成的构造方法无参数， 无返回值， 所以方法描述符为 ()V  。 这个参数对应method_info 中的descriptor_index 。
        //第四个参数是和泛型相关的， 这里传入null表示该方法不是泛型方法。这个参数对应method_info 中的Signature属性。
        //第五个参数指定方法声明可能抛出的异常。 这里无异常声明抛出， 传入null 。 这个参数对应method_info 中的Exceptions属性
        MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, _asmInitName, _asmInitDesc,null,null);
        //生成构造方法的字节码指令
        //1 调用visitVarInsn方法，生成aload指令， 将第0个本地变量（也就是this）压入操作数栈。
        //2 调用visitMethodInsn方法， 生成invokespecial指令， 调用父类（也就是Object）的构造方法。
        //3 调用visitInsn方法，生成return指令， 方法返回。
        //4 调用visitMaxs方法， 指定当前要生成的方法的最大局部变量和最大操作数栈。 对应Code属性中的max_stack和max_locals 。
        //5 最后调用visitEnd方法， 表示当前要生成的构造方法已经创建完成。
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        if (callSuper) {
            mv.visitMethodInsn(Opcodes.INVOKESPECIAL, asmClassName, _asmInitName, _asmInitDesc, false);
        }else{
            mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", _asmInitName, _asmInitDesc, false);
        }
        mv.visitInsn(Opcodes.RETURN);
        // 更新操作数栈
        mv.visitMaxs(1,1);
        // 一定要有visitEnd
        mv.visitEnd();
    }
    public static void asmDefaultConstructor(ClassWriter cw){
        asmDefaultConstructor(cw, _asmSimpleInternal, true);
    }
    public static String asmSimpleSignature(String domainClass){
        return _asmSimpleSignature + "<" + domainClass + ">;";
    }
    public static String asmVersionSimpleSignature(String domainClass){
        return _asmVersionSimpleSignature + "<" + domainClass + ">;";
    }
    public static String asmSimpleDaoSignature(String domainClass){
        return "Ljava/lang/Object;" + _asmSimpleDaoSignature + "<" + domainClass + ">;";
    }
    public static String asmSimplePersistenceImplSignature(String daoClass, String domainClass, String persistenceClass){
        return _asmSimplePersistenceImplSignature + "<" + daoClass + domainClass + ">;" + persistenceClass;
    }
    public static String asmSimplePersistenceSignature(String domainClass){
        return "Ljava/lang/Object;" + _asmSimplePersistenceSignature + "<" + domainClass + ">;";
    }
    public static String asmQSignature(String domainType){
        return "Ljava/lang/Object;" + _asmQSignature + "<" + domainType + ">;";
    }
    public static String asmSimpleServerSignature(String domainClass){
        return "Ljava/lang/Object;" + _asmSimpleServerSignature + "<" + domainClass + ">;";
    }
    public static String asmSimpleServerImplSignature(String domainClass, String serverClass){
        return _asmSimpleServerImplSignature + "<" + domainClass + ">;" + serverClass;
    }
    public static String asmFunctionApplySignature(String tClass, String rClass){
        return "(" + tClass + ")" + rClass;
    }
    public static String asmConsumerAcceptSignature(String tClass){
        return "(" + tClass + ")V";
    }
    public static void asmAddVersion(ClassWriter cw, int domainVersion) throws Exception{
        FieldVisitor fv = cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC, _asmDomainVersion, "I", null, domainVersion);
        fv.visitEnd();
    }
    public static boolean checkAsmVersion(Class<?> domainClass, int domainVersion){
        try {
            Field field = domainClass.getDeclaredField(_asmDomainVersion);
            field.setAccessible(true);
            int v = field.getInt(null);
            return domainVersion <= v;
        }catch (Exception e){
            return false;
        }
    }
    public static void asmAddOptimisticVersion(ClassWriter cw, String domainClassName) throws Exception{
        asmAddField(cw, domainClassName, _optimisticVersionField, true);
    }
    public static void asmAddField(ClassWriter cw, String domainClassName, SchemaFieldDO schemaField) throws Exception{
        asmAddField(cw, domainClassName, schemaField, false);
    }
    public static void asmAddField(ClassWriter cw, String domainClassName, SchemaFieldDO schemaField, boolean isVersionField) throws Exception{
        Class<?> classDataType = SchemaConstant.Java.type(schemaField.getClassDataType());
        String asmDataType = Type.getDescriptor(classDataType);
        FieldVisitor fv = cw.visitField(Opcodes.ACC_PRIVATE, schemaField.getClassFieldCode(), asmDataType,null,null);
        // 添加注解：@TableField
        AnnotationVisitor av = fv.visitAnnotation(_asmTableFieldType, true);
        av.visit("value", schemaField.getJdbcFieldCode());
        av.visitEnd();
        if (isVersionField){
            av = fv.visitAnnotation(_asmVersionType, true);
            av.visitEnd();
        }
        // 不要忘记end
        fv.visitEnd();
        MethodVisitor mv;
        String methodCode = StringUtils.capitalize(schemaField.getClassFieldCode());
        String getMethod = "get" + methodCode;
        String setMethod = "set" + methodCode;

        mv = cw.visitMethod(Opcodes.ACC_PUBLIC, getMethod, "()" + asmDataType, null, null);
        mv.visitCode();
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitFieldInsn(Opcodes.GETFIELD, domainClassName, schemaField.getClassFieldCode(), asmDataType);
        mv.visitInsn(Opcodes.ARETURN);
        mv.visitMaxs(1, 1);
        mv.visitEnd();
//        cw.visitEnd();
        mv = cw.visitMethod(Opcodes.ACC_PUBLIC, setMethod, "(" + asmDataType + ")V", null, null);
        mv.visitCode();
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitVarInsn(Opcodes.ALOAD, 1);
        mv.visitFieldInsn(Opcodes.PUTFIELD, domainClassName, schemaField.getClassFieldCode(), asmDataType);
        mv.visitMaxs(2, 2);
        mv.visitInsn(Opcodes.RETURN);
        mv.visitEnd();
    }
    public static void asmAddInnerCriterion(ClassWriter cw, String criterionClassName, Class<?> innerClass, String fieldCode) throws Exception{
        String asmInnerType = Type.getDescriptor(innerClass);
        FieldVisitor fv = cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL, fieldCode, asmInnerType, null, null);
        // 不要忘记end
        fv.visitEnd();
        MethodVisitor mv;
        String methodCode = StringUtils.capitalize(fieldCode);
        String getMethod = "get" + methodCode;
        String setMethod = "set" + methodCode;

        mv = cw.visitMethod(Opcodes.ACC_PUBLIC, getMethod, "()" + asmInnerType, null, null);
        mv.visitCode();
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitFieldInsn(Opcodes.GETFIELD, criterionClassName, fieldCode, asmInnerType);
        mv.visitInsn(Opcodes.ARETURN);
        mv.visitMaxs(1, 1);
        mv.visitEnd();

        mv = cw.visitMethod(Opcodes.ACC_PUBLIC, setMethod, "(" + asmInnerType + ")V", null, null);
        mv.visitCode();
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitVarInsn(Opcodes.ALOAD, 1);
        mv.visitFieldInsn(Opcodes.PUTFIELD, criterionClassName, fieldCode, asmInnerType);
        mv.visitMaxs(2, 2);
        mv.visitInsn(Opcodes.RETURN);
        mv.visitEnd();
    }
    public static void asmAddCriterion(ClassWriter cw, String criterionClassName, SchemaCriterionDO criterion) throws Exception{
        Class<?> classDataType = SchemaConstant.Java.type(criterion.getClassDataType());
        String asmDataType = Type.getDescriptor(classDataType);
        String fieldCode = criterion.getCriterionCode();
        FieldVisitor fv = cw.visitField(Opcodes.ACC_PRIVATE, fieldCode, asmDataType, null, null);
        // 不要忘记end
        fv.visitEnd();
        MethodVisitor mv;
        String methodCode = StringUtils.capitalize(fieldCode);
        String getMethod = "get" + methodCode;
        String setMethod = "set" + methodCode;

        mv = cw.visitMethod(Opcodes.ACC_PUBLIC, getMethod, "()" + asmDataType, null, null);
        mv.visitCode();
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitFieldInsn(Opcodes.GETFIELD, criterionClassName, fieldCode, asmDataType);
        mv.visitInsn(Opcodes.ARETURN);
        mv.visitMaxs(1, 1);
        mv.visitEnd();

        mv = cw.visitMethod(Opcodes.ACC_PUBLIC, setMethod, "(" + asmDataType + ")V", null, null);
        mv.visitCode();
        mv.visitVarInsn(Opcodes.ALOAD, 0);
        mv.visitVarInsn(Opcodes.ALOAD, 1);
        mv.visitFieldInsn(Opcodes.PUTFIELD, criterionClassName, fieldCode, asmDataType);
        mv.visitMaxs(2, 2);
        mv.visitInsn(Opcodes.RETURN);
        mv.visitEnd();
    }
    public static AnnotationsAttribute buildAnnotation(ConstPool constPool, String annotation){
        AnnotationsAttribute attr = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
        Annotation annot = new Annotation(annotation, constPool);
        attr.addAnnotation(annot);
        return attr;
    }
    public static ClassLoader getClassLoader(Class<?> clz){
        ClassLoader cl = null;
        try{
            cl = Thread.currentThread().getContextClassLoader();
        }catch (Throwable t){
            // Cannot access thread context ClassLoader - falling back to system class loader
        }
        if (cl == null){
            cl = clz.getClassLoader();
        }
        return cl;
    }
}
